/*
  This file is a part of hashutils package.
  (c) 2004 Zeljko Vrba <zvrba@globalnet.hr>

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <sfio.h>
#include "crypthash.h"

static char rcsid[] = "$Id: crypthash.c 2 2007-03-29 14:43:55Z zeljko.vrba $";

/**
   @file
   An implementation of Davies-Meyer MDC (Handbook of Applied Cryptography)
   based on a symmetric block cipher. Since we want 64-bit cipher, some
   values are hardcoded.
*/

unsigned long davies_meyer_IV[2] = { 0x31415926, 0x53589793 };
#define BLOCK 8					/* bytes */

/*
  DIRTY TRICK: I don't want to mess with libtomcrypt code, so I'm reusing
  struct md5_state. All variables within are large enough for 64-bit
  block hash. Nevertheless a pointer to cipher_desc is needed for block
  cipher key setup and ecb encryption.

  This will work if there is at least one hash compiled in libtomcrypt
  whose state structure is larger than md5 structure.
*/
struct davies_meyer_state {
	struct md5_state md5;
	const struct _cipher_descriptor *cipher;
};

static void davies_meyer_init(hash_state *md)
{
	_ARGCHK(md != NULL);
	md->md5.state[0] = davies_meyer_IV[0];
	md->md5.state[1] = davies_meyer_IV[1];
	md->md5.state[2] = md->md5.state[3] = 0; /* unused for 64 bit block */
	md->md5.curlen = 0;
	md->md5.length = 0;
}

/*
  Algorithm: Hi=hash value, xi=ith input block
  H0=IV (initial vector)
  Hi = E[xi](H[i-1]) ^ H[i-1]

  Since MD5 is 128-bit algorithm, we use state[0] and state[1] as current
  hash value; state[2] and state[3] is used for encryption result.
*/
static void davies_meyer_compress(hash_state *md, unsigned char *buf)
{
	int err;
	const struct _cipher_descriptor *cipher =
		((struct davies_meyer_state*)md)->cipher;
	symmetric_key skey;
	
	/* set next input block as key */
	if((err = cipher->setup(buf, BLOCK, 0, &skey)) != CRYPT_OK) {
		sfprintf(sfstderr, "FATAL: crypto error: %s, exiting.\n",
				 error_to_string(err));
		exit(1);
	}

	/* encrypt previous hash value */
	cipher->ecb_encrypt((unsigned char*)md->md5.state,
						(unsigned char*)(md->md5.state+2),
						&skey);

	/* xor encrypted with prev value to yield updated hash */
	md->md5.state[0] ^= md->md5.state[2];
	md->md5.state[1] ^= md->md5.state[3];
}

HASH_PROCESS(davies_meyer_process, davies_meyer_compress, md5, BLOCK)

static int davies_meyer_test(void)
{
	return CRYPT_NOP;
}

static int davies_meyer_done(hash_state *md, unsigned char *hash)
{
    _ARGCHK(md != NULL);
    _ARGCHK(hash != NULL);

	/*
	  length of data currently in buffer must be strictly less than block
	  size (because full blocks are processed in davies_meyer_process).
	*/
    if(md->md5.curlen >= BLOCK) {
		sfprintf(sfstderr, "davies_meyer_done: inconsistency.\n");
		return CRYPT_INVALID_ARG;
	}

    /* increase the length of the message */
    md->md5.length += md->md5.curlen * 8;

    /*
	  append the '1' bit and many zeros to fill 8-byte block. compress the
	  resulting block.
	*/
    md->md5.buf[md->md5.curlen++] = (unsigned char)0x80;
	while(md->md5.curlen < BLOCK) md->md5.buf[md->md5.curlen++] = 0;
	davies_meyer_compress(md, md->md5.buf);
	md->md5.curlen = 0;

    /* store length */
    STORE64L(md->md5.length, md->md5.buf+56);
    davies_meyer_compress(md, md->md5.buf);

    /* copy output */
	STORE32L(md->md5.state[0], hash);
	STORE32L(md->md5.state[1], hash+4);
#ifdef CLEAN_STACK
    zeromem(md, sizeof(hash_state));
#endif
    return CRYPT_OK;
}

#define DEFINE_DMHASH(Cipher, Name, Id)\
static void dm_##Cipher##_init(hash_state *md)\
{\
  davies_meyer_init(md);\
  ((struct davies_meyer_state*)md)->cipher = &Cipher##_desc;\
}\
struct _hash_descriptor dm_##Cipher##_desc = {\
  Name, Id, 8, BLOCK,\
  dm_##Cipher##_init,\
  davies_meyer_process,\
  davies_meyer_done,\
  davies_meyer_test\
};

DEFINE_DMHASH(blowfish, "DMblowfish", 20)
DEFINE_DMHASH(rc2, "DMrc2", 21)
DEFINE_DMHASH(rc5, "DMrc5", 22)
DEFINE_DMHASH(safer_k64, "DMsafer_k64", 24)
DEFINE_DMHASH(safer_sk64, "DMsafer_sk64", 25)
DEFINE_DMHASH(cast5, "DMcast5", 26)
